#include <string>

#include "ResultQuote.hpp"


/* ************************************************************************ */
// FieldValuePair
/* ************************************************************************ */

FieldValuePair::FieldValuePair(FieldName fieldName, std::string strFieldName, std::string strDataValue, FieldNameType fieldNameType)
    : fieldName(fieldName), strFieldName(strFieldName), strDataValue(strDataValue), fieldNameType(fieldNameType)
{
  
}

// Convert between MM/DD/YYYY to YYYY/MM/DD
bool FieldValuePair::convertToSQLDate(std::string strMM_DD_YYYYDate, std::string& strSQLDate)
{
  std::size_t posFirst = strMM_DD_YYYYDate.find_first_of('/');
  std::size_t posLast  = strMM_DD_YYYYDate.find_last_of('/'); 
  
  if (posFirst == std::string::npos || posLast == std::string::npos     // no slashes
         || posFirst == posLast)					// only one slash
  {
    // Invalid date, can't convert
    return false;  
  }
  
  std::string strMonth = strMM_DD_YYYYDate.substr(0, posFirst);
  std::string strDay   = strMM_DD_YYYYDate.substr(posFirst+1, posLast - posFirst - 1);
  std::string strYear  = strMM_DD_YYYYDate.substr(posLast+1);
  
  strSQLDate = strYear + '/' + strMonth + '/' + strDay;
  return true;
}


std::string FieldValuePair::getDataValue(TrimOptions trimOptions)
{
  std::string strReturn = this->strDataValue;
  
  if (trimOptions == TrimOptions::Whitespace) {
    // Trim leading and trailing whitespace
    strReturn.erase(0, strReturn.find_first_not_of(" \r\n\t"));       // leading
    strReturn.erase(strReturn.find_last_not_of(" \r\n\t")+1);         // trailing
  } // whitespace
  else if (trimOptions == TrimOptions::ExtractNumber)
  {
    // Trims leading and trailing nonnumeric characters.
    // Converts "foo -3.14 bar" to "-3.14"
    std::string strFirst("0123456789-.");
    std::string  strLast("0123456789");
    
    std::size_t posFirst = strReturn.find_first_of(strFirst);
    std::size_t posLast  = strReturn.find_last_of(strLast);  
    
    // Trim if the number is found
    if (posFirst != std::string::npos && posLast != std::string::npos)
    {
      std::size_t trimCount = posLast - posFirst;
      strReturn = strReturn.substr(posFirst, trimCount+1);
    } 
    else
    {
      // No number was found. Recurse and switch to strip whitespace only.
      strReturn = this->getDataValue(TrimOptions::Whitespace);
    }
  } // extract number
  else if (trimOptions == TrimOptions::ForSQL)
  {
    // ForSQL calls either TrimOptions::ExtractNumber or ::WhiteSpace, and converts dates to SQL format.
    // The output is meant to go directly into an SQL statement
    if (this->fieldNameType == FieldNameType::Double || this->fieldNameType == FieldNameType::Int64)
    {
      // Normal numbers: ExtractNumber, no quotes
      strReturn = this->getDataValue(TrimOptions::ExtractNumber);
    }
    else if (this->fieldNameType == FieldNameType::Date)
    {
      strReturn = this->getDataValue(TrimOptions::Whitespace);
      std::string strTempDate;
      if (this->convertToSQLDate(strReturn, strTempDate)) {
	// TODO How do we handle the date failing to convert? For now, return an empty string.
	strReturn.clear();
      }
    } 
    else 
    {
      // For all other strings, trim whitespace
      strReturn = this->getDataValue(TrimOptions::Whitespace);
    }
  } // ForSQL
  
  
  // If TrimOptions::None or an unknown option, the original string will be returned
  // Otherwise, the trimmed string is returned.
  return strReturn;
}





/* ************************************************************************ */
// ResultQuote
/* ************************************************************************ */


ResultQuote::ResultQuote(std::string quoteSymbol) : quoteSymbol(quoteSymbol)
{
  
}


void ResultQuote::addRawPair(FieldName fieldName, std::string strFieldName, std::string strDataValue, FieldNameType fieldNameType)
{
  FieldValuePair fvPair(fieldName, strFieldName, strDataValue, fieldNameType);
  
  this->mapFieldData.insert( MapFieldNameToFieldValuePair(fieldName, fvPair) );
  this->vecFieldNamesInMap.push_back(fieldName);
}



/* Returns the FieldValuePair for the given FieldType */
bool ResultQuote::getFieldValuePair(FieldName fieldName, FieldValuePair& fieldValuePair)
{
  MapFieldNameToFieldValue::const_iterator itr = this->mapFieldData.find(fieldName); 
  
  if (itr == this->mapFieldData.end()) {   // Not found in map
    return false;	  
  }
  
  fieldValuePair = itr->second;
  return true;
}

/* Convinence, values are extracted for caller */
bool ResultQuote::getFieldValuePair(FieldName fieldName, std::string& strFieldName, std::string& strDataValue, FieldNameType& fieldNameType, TrimOptions trimOptions)
{
  FieldValuePair fv;
  
  if (!(this->getFieldValuePair(fieldName, fv))) {
    return false;  
  }
  
  strFieldName  = fv.getFieldNameString();
  strDataValue  = fv.getDataValue(trimOptions);
  fieldNameType = fv.getFieldNameType();
  
  return true;
}


// NOTE: strNumber should first be trimmed with TrimOptions::ExtractNumber by the caller
// Attempts to convert strNumber to a numeric FieldNameType, such as double or Int64
// If the conversion fails, or strNumber is empty, the return will be an empty string
// If the conversion succeeds, the return will be the number converted back into a string
// This filters out any numeric fields that contain "N/A" or other unexpected strings
// The empty string will end up in the SQL table as a NULL
std::string ResultQuote::verifyNumber(std::string strNumber, FieldNameType fieldNameType)
{
  std::string strReturn;
  
  try {
    if (fieldNameType == FieldNameType::Double) 
    {
      double valDouble = std::stod(strNumber);
      strReturn = std::to_string(valDouble);
    } 
    else if (fieldNameType == FieldNameType::Int64) 
    {
      int64_t valInt64 = std::strtoll(strNumber.c_str(), 0, 10);
      strReturn = std::to_string(valInt64);
    } 
    else 
    {
      strReturn = strNumber;  // Return the original value if type not found
    }
  } catch (std::exception &e) {
    // Conversion failure, return an empty string
    strReturn.clear();
  }
  
  return strReturn;
}


bool ResultQuote::lookupFieldNameString(FieldName fieldName, std::string& strFieldName)
{
  FieldValuePair fv;
  if ( !(this->getFieldValuePair(fieldName, fv)) ) { return false; }
  strFieldName = fv.getFieldNameString();
  return true;
}


bool ResultQuote::lookupDataString(FieldName fieldName, std::string& strDataString, TrimOptions trimOptions, bool bVerifyNumber)
{
  FieldValuePair fv;
  if ( !(this->getFieldValuePair(fieldName, fv)) ) { return false; }
  strDataString = fv.getDataValue(trimOptions);
  if (bVerifyNumber) {
    strDataString = this->verifyNumber(strDataString, fv.getFieldNameType());  
  }
  
  return true;
}


bool ResultQuote::lookupFieldType(FieldName fieldName, FieldNameType& fieldNameType)
{
  FieldValuePair fv;
  if ( !(this->getFieldValuePair(fieldName, fv)) ) { return false; }
  fieldNameType = fv.getFieldNameType();
  return true;
}







